﻿/*
 *    Copyright © 2012 FPInst authors
 *
 *      This file is part of the FPInst project 
 *      (for more information, see: http://code.google.com/p/fpinst/).
 *
 *    FPInst is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    FPInst is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with FPInst.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using USB;
using System.Diagnostics;
using RotSw;

namespace SignalGen
{
    public partial class frmSignalGen : Form
    {
        private Device device;
        private Hardware hw;

        private Waveform.WaveType CurrentWave;

        private byte[][] bw = new byte[7][];
        private byte[] cbw;

        private ToolTip ttduty = new ToolTip();
        private ToolTip ttphase = new ToolTip();
        private ToolTip ttoffset = new ToolTip();
        private ToolTip ttamp = new ToolTip();

        private double currentfreq;
        private double startfreq;
        private double endfreq;
        private double cursorfreq;
        private double currentphase;

        public frmSignalGen()
        {
            device = new Device();  // Initialize default device

            InitializeComponent();
            Application.AddMessageFilter(new MouseMessageFilter());            
        }

        private void SignalGen_Load(object sender, EventArgs e)
        {
            hw = new Hardware(new Hardware.ConnectDelegate(HandleHwConnect), new Hardware.DisconnectDelegate(HandleHwDisconnect));

            hw.Connect();
            //device = new Device(); // TODO: Test

            tbFreq.Text = currentfreq.ToString("000,000,000.000");
            tbStartFreq.Text = startfreq.ToString("000,000,000.000");
            tbEndFreq.Text = endfreq.ToString("000,000,000.000");
            tbCursor.Text = cursorfreq.ToString("000,000,000.000");

            SetUpHw();

            rb4.Checked = true;
            CurrentWave = Waveform.WaveType.Sinus;

            ttduty.SetToolTip(rsDuty, String.Format("{0:P1}", rsDuty.Value / 1000d));
            ttphase.SetToolTip(rsPhase, String.Format("{0:F2}°", (rsPhase.Value * (360f / rsPhase.Maxvalue)) - 180));
            ttoffset.SetToolTip(rsOffset, String.Format("{0:P1}", (float)rsOffset.Value / rsOffset.Maxvalue));
            ttamp.SetToolTip(rsAmp, String.Format("{0:P1}", (float)rsAmp.Value / rsAmp.Maxvalue));

            RefreshWaveButtons();
            UpdateWave();
        }

        public void SetUpHw()
        {
            bw[4] = Waveform.Generate(Waveform.WaveType.Sinus, device.FPGASampleNum, (Math.Pow(2, device.FPGASampleResolution) - 0.5), 50);
            bw[1] = Waveform.Generate(Waveform.WaveType.Cosinus, device.FPGASampleNum, (Math.Pow(2, device.FPGASampleResolution) - 0.5), 50);
            bw[5] = Waveform.Generate(Waveform.WaveType.Triangle, device.FPGASampleNum, (Math.Pow(2, device.FPGASampleResolution) - 0.5), 50);
            bw[2] = Waveform.Generate(Waveform.WaveType.Square, device.FPGASampleNum, (Math.Pow(2, device.FPGASampleResolution) - 0.5), 50);
            bw[6] = Waveform.Generate(Waveform.WaveType.Noise, device.FPGASampleNum, (Math.Pow(2, device.FPGASampleResolution) - 0.5), 50);
            bw[3] = Waveform.Generate(Waveform.WaveType.Null, device.FPGASampleNum, (Math.Pow(2, device.FPGASampleResolution) - 0.5), 50);

            rsPhase.Minvalue = 0;
            rsPhase.Maxvalue = (1 << device.FPGAPhaseResolution) - 1;
            rsPhase.Value = rsPhase.Maxvalue / 2;

            rsOffset.Minvalue = 0;
            rsOffset.Maxvalue = device.MaxOffset;
            rsOffset.Value = rsOffset.Maxvalue / 2;

            rsAmp.Minvalue = 0;
            rsAmp.Maxvalue = (1 << device.AmpResolution) - 1;
            rsAmp.Value = rsAmp.Maxvalue / 2;

            UpdateWave();

            FreqTask ft = new FreqTask(currentfreq);
            hw.AddTask(ft);

            WaveTask wt = new WaveTask(cbw);
            hw.AddTask(wt);

            PhaseTask pt = new PhaseTask(currentphase);
            hw.AddTask(pt);

            OffsetTask ot = new OffsetTask(rsOffset.Value);
            hw.AddTask(ot);

            AmpTask at = new AmpTask(rsAmp.Value);
            hw.AddTask(at);

            SweepStateChanged();
        }

        public void HandleHwConnect(Device dev)
        {
            this.Invoke(new Hardware.ConnectDelegate(HandleHwConnectLocal), dev);
        }

        public void HandleHwConnectLocal(Device dev)
        {

            device = dev;
            tssLabel.Text = "Connected";
            tssLabel.BackColor = Color.PaleGreen;
            tssDevSerial.Text = "Device S/N: " + device.Serial;

            rsAmp.Enabled = true;
            rsOffset.Enabled = true;
            rsPhase.Enabled = true;
            rsDuty.Enabled = true;

            rsMeg.Enabled = true;
            rsKil.Enabled = true;
            rsOne.Enabled = true;
            rsMil.Enabled = true;

            cbSweep.Enabled = true;

            if (cbSweep.Checked == true) cbSweep.Checked = false;   // turn off sweep on connect
            SetUpHw();
        }

        public void HandleHwDisconnect()
        {
            this.Invoke(new Hardware.DisconnectDelegate(HandleHwDisconnectLocal));
        }

        public void HandleHwDisconnectLocal()
        {
            tssLabel.Text = "Disconnected";
            tssLabel.BackColor = Color.LightCoral;
            tssDevSerial.Text = "";

            rsAmp.Enabled = false;
            rsOffset.Enabled = false;
            rsPhase.Enabled = false;
            rsDuty.Enabled = false;

            rsMeg.Enabled = false;
            rsKil.Enabled = false;
            rsOne.Enabled = false;
            rsMil.Enabled = false;

            cbSweep.Enabled = false;
        }

        private void RefreshWaveButtons()
        {
            rb4.Image = DrawWave(rb1.Width - 10, rb1.Height - 10, bw[4]);
            rb1.Image = DrawWave(rb1.Width - 10, rb1.Height - 10, bw[1]);
            rb5.Image = DrawWave(rb1.Width - 10, rb1.Height - 10, bw[5]);
            rb2.Image = DrawWave(rb1.Width - 10, rb1.Height - 10, bw[2]);
            rb6.Image = DrawWave(rb1.Width - 10, rb1.Height - 10, bw[6]);
            rb3.Image = DrawWave(rb1.Width - 10, rb1.Height - 10, bw[3]);
        }

        private void SignalGen_FormClosing(object sender, FormClosingEventArgs e)
        {
            hw.Disconnect();
        }

        private void rswitches_ValueChanged(object sender, CancelEventArgs e)
        {           
            long maxfreq = (1 << device.FPGAFreqResolution) - 1;
            double freq = (rsMil.Value * 0.001D) + rsOne.Value + (rsKil.Value * 1000D) + (rsMeg.Value * 1000000D);
            double minstep = (double)device.FPGAFreq / (1L << device.FPGAAccResolution);
            long dfreq = (long)(freq / minstep) + 1;
            if (dfreq > maxfreq)
            {
                e.Cancel = true;
                return;
            }
            //double cfreq = dfreq * minstep;
            string sfreq = freq.ToString("000,000,000.000");
            tbFreq.Text = sfreq;
            currentfreq = freq;

            FreqTask ft = new FreqTask(freq);
            hw.AddTask(ft);
        }

        private Image DrawWave(int xsize, int ysize, byte[] wave, float phase = 0, bool drawscale = false)
        {
            // prepare graphics
            Bitmap b = new Bitmap(xsize, ysize);
            Graphics g = Graphics.FromImage(b);
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            g.FillRectangle(Brushes.Transparent, 0, 0, xsize, ysize);

            // draws scale for the big display
            if (drawscale == true)
            {
                // offset moves the scale up or down
                float offset = ysize * ((float)rsOffset.Value / rsOffset.Maxvalue - 0.5f);

                // draw axes
                g.DrawLine(Pens.DarkBlue, 0, 0, 0, ysize);
                g.DrawLine(Pens.DarkBlue, 0, (Single)ysize / 2 + offset, xsize, (Single)ysize / 2 + offset);

                // draw vertical lines
                for (int i = 1; i < 4; i++)
                {
                    g.DrawLine(Pens.DarkGray, (float)xsize / 4 * i, 0, (float)xsize / 4 * i, ysize);
                }

                // distance between horizontal lines
                float scale = (ysize - (ysize * ((float)rsAmp.Value / rsAmp.Maxvalue))) / 4 + ysize / 8;

                // draw horizontal lines
                for (int i = 1; i < ysize / scale; i++)
                {
                    g.DrawLine(Pens.DarkGray, 0, (Single)ysize / 2 - scale * i + offset, xsize, (Single)ysize / 2 - scale * i + offset);
                    g.DrawLine(Pens.DarkGray, 0, (Single)ysize / 2 + scale * i + offset, xsize, (Single)ysize / 2 + scale * i + offset);
                }
            }
            else
            {
                // draw axes
                g.DrawLine(Pens.DarkGray, 0, 0, 0, ysize);
                g.DrawLine(Pens.DarkGray, 0, (Single)ysize / 2, xsize, (Single)ysize / 2);
            }

            // draw wave
            double scalex = (double)xsize / (double)wave.Length;
            double scaley = (double)(ysize / (double)(Math.Pow(2,device.FPGASampleResolution)));
            int iphase = (int)(phase * (wave.Length / rsPhase.Maxvalue));

            for (int i = 0; i < wave.Length-1; i++)
            {
                int ip = i + iphase; if (ip < 0) ip += wave.Length; if (ip >= wave.Length) ip -= wave.Length;
                int ip1 = ip + 1; if (ip1 < 0) ip1 += wave.Length; if (ip1 >= wave.Length) ip1 -= wave.Length;
                double p1 = (wave[ip] * scaley) * 0.9 + (ysize * 0.05);
                double p2 = (wave[ip1] * scaley) * 0.9 + (ysize * 0.05);
                g.DrawLine(Pens.Black, (Single)(i * scalex), (Single)p1, (Single)((i + 1) * scalex), (Single)p2);
            }
            return b;
        }

        private void rbwaveform_CheckedChanged(object sender, EventArgs e)
        {
            if (rb4.Checked == true) CurrentWave = Waveform.WaveType.Sinus;
            if (rb1.Checked == true) CurrentWave = Waveform.WaveType.Cosinus;
            if (rb5.Checked == true) CurrentWave = Waveform.WaveType.Triangle;
            if (rb2.Checked == true) CurrentWave = Waveform.WaveType.Square;
            if (rb6.Checked == true) CurrentWave = Waveform.WaveType.Noise;
            if (rb3.Checked == true) CurrentWave = Waveform.WaveType.Null;

            UpdateWave();

            WaveTask wt = new WaveTask(cbw);
            hw.AddTask(wt);
        }

        private void UpdateWave()
        {
            currentphase = rsPhase.Value - rsPhase.Maxvalue / 2;
            cbw = Waveform.Generate(CurrentWave, device.FPGASampleNum, (Math.Pow(2, device.FPGASampleResolution) - 0.5), rsDuty.Value / 10);
            pbWave.Image = DrawWave(pbWave.Width - 10, pbWave.Height - 10, cbw, (float)currentphase, true);
        }

        private void rsDuty_ValueChanged(object sender, CancelEventArgs e)
        {
            UpdateWave();
            ttduty.SetToolTip(rsDuty, String.Format("{0:P1}", rsDuty.Value / 1000d));

            WaveTask wt = new WaveTask(cbw);
            hw.AddTask(wt);
        }

        private void rsPhase_ValueChanged(object sender, CancelEventArgs e)
        {
            UpdateWave();
            ttphase.SetToolTip(rsPhase, String.Format("{0:F2}°", (rsPhase.Value * (360f / rsPhase.Maxvalue)) - 180));

            PhaseTask pt = new PhaseTask(currentphase);
            hw.AddTask(pt);
        }

        private void rsDuty_Click(object sender, EventArgs e)
        {
            rsDuty.Value = 500;
        }

        private void rsPhase_Click(object sender, EventArgs e)
        {
            rsPhase.Value = rsPhase.Maxvalue / 2;
        }

        private void rsOffset_ValueChanged(object sender, CancelEventArgs e)
        {
            UpdateWave();
            ttoffset.SetToolTip(rsOffset, String.Format("{0:P1}", (float)rsOffset.Value / rsOffset.Maxvalue));

            OffsetTask ot = new OffsetTask(rsOffset.Value);
            hw.AddTask(ot);
        }

        private void rsAmp_ValueChanged(object sender, CancelEventArgs e)
        {
            UpdateWave();
            ttamp.SetToolTip(rsAmp, String.Format("{0:P1}", (float)rsAmp.Value / rsAmp.Maxvalue));

            AmpTask at = new AmpTask(rsAmp.Value);
            hw.AddTask(at);
        }

        private void rsOffset_DoubleClick(object sender, EventArgs e)
        {
            rsOffset.Value = rsOffset.Maxvalue / 2;
        }

        private void rsAmp_DoubleClick(object sender, EventArgs e)
        {
            rsAmp.Value = rsAmp.Maxvalue / 2;
        }

        private void btnStartFreq_Click(object sender, EventArgs e)
        {
            tbStartFreq.Text = tbFreq.Text;
            startfreq = currentfreq;
            CalculateCursor();
            SweepStateChanged();
        }

        private void btnEndFreq_Click(object sender, EventArgs e)
        {
            tbEndFreq.Text = tbFreq.Text;
            endfreq = currentfreq;
            CalculateCursor();
            SweepStateChanged();
        }

        private void rsCursor_ValueChanged(object sender, CancelEventArgs e)
        {
            CalculateCursor();
            SweepStateChanged();
        }

        private void CalculateCursor()
        {
            cursorfreq = (endfreq - startfreq) * ((float)rsCursor.Value / rsCursor.Maxvalue) + startfreq;
            tbCursor.Text = cursorfreq.ToString("000,000,000.000");
        }

        // TODO: Log mode not implemented due to memory shortage on PIC
        private void btnLinLog_Click(object sender, EventArgs e)
        {
            if (btnLinLog.Text == "Lin") btnLinLog.Text = "Log";
            else if (btnLinLog.Text == "Log") btnLinLog.Text = "Lin";

            SweepStateChanged();
        }

        private void cbSweep_CheckedChanged(object sender, EventArgs e)
        {
            SweepStateChanged();
        }

        private void SweepStateChanged()
        {
            byte sweepstate = 0;
            if (cbSweep.Checked == true)
            {
                if (btnLinLog.Text == "Lin") sweepstate = 1;
                if (btnLinLog.Text == "Log") sweepstate = 2;
            }

            SweepTask st = new SweepTask(startfreq, endfreq, cursorfreq, sweepstate);
            hw.AddTask(st);
        }

        private void btnClk_Click(object sender, EventArgs e)
        {
            Packet p = new Packet(Packet.eType.MANCLK, new byte[1]);
            hw.Execute(p);
        }

    }
}
